1   /*
2    * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4    *
5    * This code is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU General Public License version 2 only, as
7    * published by the Free Software Foundation.
8    *
9    * This code is distributed in the hope that it will be useful, but WITHOUT
10   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12   * version 2 for more details (a copy is included in the LICENSE file that
13   * accompanied this code).
14   *
15   * You should have received a copy of the GNU General Public License version
16   * 2 along with this work; if not, write to the Free Software Foundation,
17   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18   *
19   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20   * or visit www.oracle.com if you need additional information or have any
21   * questions.
22   */
23  
24  /*
25   * @test
26   * @bug 4199068 4738465 4937983 4930681 4926230 4931433 4932663 4986689
27   *      5026830 5023243 5070673 4052517 4811767 6192449 6397034 6413313
28   *      6464154 6523983 6206031 4960438 6631352 6631966 6850957 6850958
29   *      4947220 7018606 7034570
30   * @summary Basic tests for Process and Environment Variable code
31   * @run main/othervm/timeout=300 Basic
32   * @author Martin Buchholz
33   */
34  
35  import java.lang.ProcessBuilder.Redirect;
36  import static java.lang.ProcessBuilder.Redirect.*;
37  
38  import java.io.*;
39  import java.util.*;
40  import java.util.concurrent.CountDownLatch;
41  import java.security.*;
42  import java.util.regex.Pattern;
43  import static java.lang.System.getenv;
44  import static java.lang.System.out;
45  import static java.lang.Boolean.TRUE;
46  import static java.util.AbstractMap.SimpleImmutableEntry;
47  
48  public class Basic {
49  
50      /* used for Windows only */
51      static final String systemRoot = System.getenv("SystemRoot");
52  
53      private static String commandOutput(Reader r) throws Throwable {
54          StringBuilder sb = new StringBuilder();
55          int c;
56          while ((c = r.read()) > 0)
57              if (c != '\r')
58                  sb.append((char) c);
59          return sb.toString();
60      }
61  
62      private static String commandOutput(Process p) throws Throwable {
63          check(p.getInputStream()  == p.getInputStream());
64          check(p.getOutputStream() == p.getOutputStream());
65          check(p.getErrorStream()  == p.getErrorStream());
66          Reader r = new InputStreamReader(p.getInputStream(),"UTF-8");
67          String output = commandOutput(r);
68          equal(p.waitFor(), 0);
69          equal(p.exitValue(), 0);
70          return output;
71      }
72  
73      private static String commandOutput(ProcessBuilder pb) {
74          try {
75              return commandOutput(pb.start());
76          } catch (Throwable t) {
77              String commandline = "";
78              for (String arg : pb.command())
79                  commandline += " " + arg;
80              System.out.println("Exception trying to run process: " + commandline);
81              unexpected(t);
82              return "";
83          }
84      }
85  
86      private static String commandOutput(String...command) {
87          try {
88              return commandOutput(Runtime.getRuntime().exec(command));
89          } catch (Throwable t) {
90              String commandline = "";
91              for (String arg : command)
92                  commandline += " " + arg;
93              System.out.println("Exception trying to run process: " + commandline);
94              unexpected(t);
95              return "";
96          }
97      }
98  
99      private static void checkCommandOutput(ProcessBuilder pb,
100                                            String expected,
101                                            String failureMsg) {
102         String got = commandOutput(pb);
103         check(got.equals(expected),
104               failureMsg + "\n" +
105               "Expected: \"" + expected + "\"\n" +
106               "Got: \"" + got + "\"");
107     }
108 
109     private static String absolutifyPath(String path) {
110         StringBuilder sb = new StringBuilder();
111         for (String file : path.split(File.pathSeparator)) {
112             if (sb.length() != 0)
113                 sb.append(File.pathSeparator);
114             sb.append(new File(file).getAbsolutePath());
115         }
116         return sb.toString();
117     }
118 
119     // compare windows-style, by canonicalizing to upper case,
120     // not lower case as String.compareToIgnoreCase does
121     private static class WindowsComparator
122         implements Comparator<String> {
123         public int compare(String x, String y) {
124             return x.toUpperCase(Locale.US)
125                 .compareTo(y.toUpperCase(Locale.US));
126         }
127     }
128 
129     private static String sortedLines(String lines) {
130         String[] arr = lines.split("\n");
131         List<String> ls = new ArrayList<String>();
132         for (String s : arr)
133             ls.add(s);
134         Collections.sort(ls, new WindowsComparator());
135         StringBuilder sb = new StringBuilder();
136         for (String s : ls)
137             sb.append(s + "\n");
138         return sb.toString();
139     }
140 
141     private static void compareLinesIgnoreCase(String lines1, String lines2) {
142         if (! (sortedLines(lines1).equalsIgnoreCase(sortedLines(lines2)))) {
143             String dashes =
144                 "-----------------------------------------------------";
145             out.println(dashes);
146             out.print(sortedLines(lines1));
147             out.println(dashes);
148             out.print(sortedLines(lines2));
149             out.println(dashes);
150             out.println("sizes: " + sortedLines(lines1).length() +
151                         " " + sortedLines(lines2).length());
152 
153             fail("Sorted string contents differ");
154         }
155     }
156 
157     private static final Runtime runtime = Runtime.getRuntime();
158 
159     private static final String[] winEnvCommand = {"cmd.exe", "/c", "set"};
160 
161     private static String winEnvFilter(String env) {
162         return env.replaceAll("\r", "")
163             .replaceAll("(?m)^(?:COMSPEC|PROMPT|PATHEXT)=.*\n","");
164     }
165 
166     private static String unixEnvProg() {
167         return new File("/usr/bin/env").canExecute() ? "/usr/bin/env"
168             : "/bin/env";
169     }
170 
171     private static String nativeEnv(String[] env) {
172         try {
173             if (Windows.is()) {
174                 return winEnvFilter
175                     (commandOutput(runtime.exec(winEnvCommand, env)));
176             } else {
177                 return commandOutput(runtime.exec(unixEnvProg(), env));
178             }
179         } catch (Throwable t) { throw new Error(t); }
180     }
181 
182     private static String nativeEnv(ProcessBuilder pb) {
183         try {
184             if (Windows.is()) {
185                 pb.command(winEnvCommand);
186                 return winEnvFilter(commandOutput(pb));
187             } else {
188                 pb.command(new String[]{unixEnvProg()});
189                 return commandOutput(pb);
190             }
191         } catch (Throwable t) { throw new Error(t); }
192     }
193 
194     private static void checkSizes(Map<String,String> environ, int size) {
195         try {
196             equal(size, environ.size());
197             equal(size, environ.entrySet().size());
198             equal(size, environ.keySet().size());
199             equal(size, environ.values().size());
200 
201             boolean isEmpty = (size == 0);
202             equal(isEmpty, environ.isEmpty());
203             equal(isEmpty, environ.entrySet().isEmpty());
204             equal(isEmpty, environ.keySet().isEmpty());
205             equal(isEmpty, environ.values().isEmpty());
206         } catch (Throwable t) { unexpected(t); }
207     }
208 
209     private interface EnvironmentFrobber {
210         void doIt(Map<String,String> environ);
211     }
212 
213     private static void testVariableDeleter(EnvironmentFrobber fooDeleter) {
214         try {
215             Map<String,String> environ = new ProcessBuilder().environment();
216             environ.put("Foo", "BAAR");
217             fooDeleter.doIt(environ);
218             equal(environ.get("Foo"), null);
219             equal(environ.remove("Foo"), null);
220         } catch (Throwable t) { unexpected(t); }
221     }
222 
223     private static void testVariableAdder(EnvironmentFrobber fooAdder) {
224         try {
225             Map<String,String> environ = new ProcessBuilder().environment();
226             environ.remove("Foo");
227             fooAdder.doIt(environ);
228             equal(environ.get("Foo"), "Bahrein");
229         } catch (Throwable t) { unexpected(t); }
230     }
231 
232     private static void testVariableModifier(EnvironmentFrobber fooModifier) {
233         try {
234             Map<String,String> environ = new ProcessBuilder().environment();
235             environ.put("Foo","OldValue");
236             fooModifier.doIt(environ);
237             equal(environ.get("Foo"), "NewValue");
238         } catch (Throwable t) { unexpected(t); }
239     }
240 
241     private static void printUTF8(String s) throws IOException {
242         out.write(s.getBytes("UTF-8"));
243     }
244 
245     private static String getenvAsString(Map<String,String> environment) {
246         StringBuilder sb = new StringBuilder();
247         for (Map.Entry<String,String> e : environment.entrySet())
248             // Ignore magic environment variables added by the launcher
249             if (! e.getKey().equals("NLSPATH") &&
250                 ! e.getKey().equals("XFILESEARCHPATH") &&
251                 ! e.getKey().equals("LD_LIBRARY_PATH"))
252                 sb.append(e.getKey())
253                     .append('=')
254                     .append(e.getValue())
255                     .append(',');
256         return sb.toString();
257     }
258 
259     static void print4095(OutputStream s, byte b) throws Throwable {
260         byte[] bytes = new byte[4095];
261         Arrays.fill(bytes, b);
262         s.write(bytes);         // Might hang!
263     }
264 
265     static void checkPermissionDenied(ProcessBuilder pb) {
266         try {
267             pb.start();
268             fail("Expected IOException not thrown");
269         } catch (IOException e) {
270             String m = e.getMessage();
271             if (EnglishUnix.is() &&
272                 ! matches(m, "Permission denied"))
273                 unexpected(e);
274         } catch (Throwable t) { unexpected(t); }
275     }
276 
277     public static class JavaChild {
278         public static void main(String args[]) throws Throwable {
279             String action = args[0];
280             if (action.equals("sleep")) {
281                 Thread.sleep(10 * 60 * 1000L);
282             } else if (action.equals("testIO")) {
283                 String expected = "standard input";
284                 char[] buf = new char[expected.length()+1];
285                 int n = new InputStreamReader(System.in).read(buf,0,buf.length);
286                 if (n != expected.length())
287                     System.exit(5);
288                 if (! new String(buf,0,n).equals(expected))
289                     System.exit(5);
290                 System.err.print("standard error");
291                 System.out.print("standard output");
292             } else if (action.equals("testInheritIO")) {
293                 List<String> childArgs = new ArrayList<String>(javaChildArgs);
294                 childArgs.add("testIO");
295                 ProcessBuilder pb = new ProcessBuilder(childArgs);
296                 pb.inheritIO();
297                 ProcessResults r = run(pb);
298                 if (! r.out().equals(""))
299                     System.exit(7);
300                 if (! r.err().equals(""))
301                     System.exit(8);
302                 if (r.exitValue() != 0)
303                     System.exit(9);
304             } else if (action.equals("System.getenv(String)")) {
305                 String val = System.getenv(args[1]);
306                 printUTF8(val == null ? "null" : val);
307             } else if (action.equals("System.getenv(\\u1234)")) {
308                 String val = System.getenv("\u1234");
309                 printUTF8(val == null ? "null" : val);
310             } else if (action.equals("System.getenv()")) {
311                 printUTF8(getenvAsString(System.getenv()));
312             } else if (action.equals("ArrayOOME")) {
313                 Object dummy;
314                 switch(new Random().nextInt(3)) {
315                 case 0: dummy = new Integer[Integer.MAX_VALUE]; break;
316                 case 1: dummy = new double[Integer.MAX_VALUE];  break;
317                 case 2: dummy = new byte[Integer.MAX_VALUE][];  break;
318                 default: throw new InternalError();
319                 }
320             } else if (action.equals("pwd")) {
321                 printUTF8(new File(System.getProperty("user.dir"))
322                           .getCanonicalPath());
323             } else if (action.equals("print4095")) {
324                 print4095(System.out, (byte) '!');
325                 print4095(System.err, (byte) 'E');
326                 System.exit(5);
327             } else if (action.equals("OutErr")) {
328                 // You might think the system streams would be
329                 // buffered, and in fact they are implemented using
330                 // BufferedOutputStream, but each and every print
331                 // causes immediate operating system I/O.
332                 System.out.print("out");
333                 System.err.print("err");
334                 System.out.print("out");
335                 System.err.print("err");
336             } else if (action.equals("null PATH")) {
337                 equal(System.getenv("PATH"), null);
338                 check(new File("/bin/true").exists());
339                 check(new File("/bin/false").exists());
340                 ProcessBuilder pb1 = new ProcessBuilder();
341                 ProcessBuilder pb2 = new ProcessBuilder();
342                 pb2.environment().put("PATH", "anyOldPathIgnoredAnyways");
343                 ProcessResults r;
344 
345                 for (final ProcessBuilder pb :
346                          new ProcessBuilder[] {pb1, pb2}) {
347                     pb.command("true");
348                     equal(run(pb).exitValue(), True.exitValue());
349 
350                     pb.command("false");
351                     equal(run(pb).exitValue(), False.exitValue());
352                 }
353 
354                 if (failed != 0) throw new Error("null PATH");
355             } else if (action.equals("PATH search algorithm")) {
356                 equal(System.getenv("PATH"), "dir1:dir2:");
357                 check(new File("/bin/true").exists());
358                 check(new File("/bin/false").exists());
359                 String[] cmd = {"prog"};
360                 ProcessBuilder pb1 = new ProcessBuilder(cmd);
361                 ProcessBuilder pb2 = new ProcessBuilder(cmd);
362                 ProcessBuilder pb3 = new ProcessBuilder(cmd);
363                 pb2.environment().put("PATH", "anyOldPathIgnoredAnyways");
364                 pb3.environment().remove("PATH");
365 
366                 for (final ProcessBuilder pb :
367                          new ProcessBuilder[] {pb1, pb2, pb3}) {
368                     try {
369                         // Not on PATH at all; directories don't exist
370                         try {
371                             pb.start();
372                             fail("Expected IOException not thrown");
373                         } catch (IOException e) {
374                             String m = e.getMessage();
375                             if (EnglishUnix.is() &&
376                                 ! matches(m, "No such file"))
377                                 unexpected(e);
378                         } catch (Throwable t) { unexpected(t); }
379 
380                         // Not on PATH at all; directories exist
381                         new File("dir1").mkdirs();
382                         new File("dir2").mkdirs();
383                         try {
384                             pb.start();
385                             fail("Expected IOException not thrown");
386                         } catch (IOException e) {
387                             String m = e.getMessage();
388                             if (EnglishUnix.is() &&
389                                 ! matches(m, "No such file"))
390                                 unexpected(e);
391                         } catch (Throwable t) { unexpected(t); }
392 
393                         // Can't execute a directory -- permission denied
394                         // Report EACCES errno
395                         new File("dir1/prog").mkdirs();
396                         checkPermissionDenied(pb);
397 
398                         // continue searching if EACCES
399                         copy("/bin/true", "dir2/prog");
400                         equal(run(pb).exitValue(), True.exitValue());
401                         new File("dir1/prog").delete();
402                         new File("dir2/prog").delete();
403 
404                         new File("dir2/prog").mkdirs();
405                         copy("/bin/true", "dir1/prog");
406                         equal(run(pb).exitValue(), True.exitValue());
407 
408                         // Check empty PATH component means current directory.
409                         //
410                         // While we're here, let's test different kinds of
411                         // Unix executables, and PATH vs explicit searching.
412                         new File("dir1/prog").delete();
413                         new File("dir2/prog").delete();
414                         for (String[] command :
415                                  new String[][] {
416                                      new String[] {"./prog"},
417                                      cmd}) {
418                             pb.command(command);
419                             File prog = new File("./prog");
420                             // "Normal" binaries
421                             copy("/bin/true", "./prog");
422                             equal(run(pb).exitValue(),
423                                   True.exitValue());
424                             copy("/bin/false", "./prog");
425                             equal(run(pb).exitValue(),
426                                   False.exitValue());
427                             prog.delete();
428                             // Interpreter scripts with #!
429                             setFileContents(prog, "#!/bin/true\n");
430                             prog.setExecutable(true);
431                             equal(run(pb).exitValue(),
432                                   True.exitValue());
433                             prog.delete();
434                             setFileContents(prog, "#!/bin/false\n");
435                             prog.setExecutable(true);
436                             equal(run(pb).exitValue(),
437                                   False.exitValue());
438                             // Traditional shell scripts without #!
439                             setFileContents(prog, "exec /bin/true\n");
440                             prog.setExecutable(true);
441                             equal(run(pb).exitValue(),
442                                   True.exitValue());
443                             prog.delete();
444                             setFileContents(prog, "exec /bin/false\n");
445                             prog.setExecutable(true);
446                             equal(run(pb).exitValue(),
447                                   False.exitValue());
448                             prog.delete();
449                         }
450 
451                         // Test Unix interpreter scripts
452                         File dir1Prog = new File("dir1/prog");
453                         dir1Prog.delete();
454                         pb.command(new String[] {"prog", "world"});
455                         setFileContents(dir1Prog, "#!/bin/echo hello\n");
456                         checkPermissionDenied(pb);
457                         dir1Prog.setExecutable(true);
458                         equal(run(pb).out(), "hello dir1/prog world\n");
459                         equal(run(pb).exitValue(), True.exitValue());
460                         dir1Prog.delete();
461                         pb.command(cmd);
462 
463                         // Test traditional shell scripts without #!
464                         setFileContents(dir1Prog, "/bin/echo \"$@\"\n");
465                         pb.command(new String[] {"prog", "hello", "world"});
466                         checkPermissionDenied(pb);
467                         dir1Prog.setExecutable(true);
468                         equal(run(pb).out(), "hello world\n");
469                         equal(run(pb).exitValue(), True.exitValue());
470                         dir1Prog.delete();
471                         pb.command(cmd);
472 
473                         // If prog found on both parent and child's PATH,
474                         // parent's is used.
475                         new File("dir1/prog").delete();
476                         new File("dir2/prog").delete();
477                         new File("prog").delete();
478                         new File("dir3").mkdirs();
479                         copy("/bin/true", "dir1/prog");
480                         copy("/bin/false", "dir3/prog");
481                         pb.environment().put("PATH","dir3");
482                         equal(run(pb).exitValue(), True.exitValue());
483                         copy("/bin/true", "dir3/prog");
484                         copy("/bin/false", "dir1/prog");
485                         equal(run(pb).exitValue(), False.exitValue());
486 
487                     } finally {
488                         // cleanup
489                         new File("dir1/prog").delete();
490                         new File("dir2/prog").delete();
491                         new File("dir3/prog").delete();
492                         new File("dir1").delete();
493                         new File("dir2").delete();
494                         new File("dir3").delete();
495                         new File("prog").delete();
496                     }
497                 }
498 
499                 if (failed != 0) throw new Error("PATH search algorithm");
500             }
501             else throw new Error("JavaChild invocation error");
502         }
503     }
504 
505     private static void copy(String src, String dst) {
506         system("/bin/cp", "-fp", src, dst);
507     }
508 
509     private static void system(String... command) {
510         try {
511             ProcessBuilder pb = new ProcessBuilder(command);
512             ProcessResults r = run(pb.start());
513             equal(r.exitValue(), 0);
514             equal(r.out(), "");
515             equal(r.err(), "");
516         } catch (Throwable t) { unexpected(t); }
517     }
518 
519     private static String javaChildOutput(ProcessBuilder pb, String...args) {
520         List<String> list = new ArrayList<String>(javaChildArgs);
521         for (String arg : args)
522             list.add(arg);
523         pb.command(list);
524         return commandOutput(pb);
525     }
526 
527     private static String getenvInChild(ProcessBuilder pb) {
528         return javaChildOutput(pb, "System.getenv()");
529     }
530 
531     private static String getenvInChild1234(ProcessBuilder pb) {
532         return javaChildOutput(pb, "System.getenv(\\u1234)");
533     }
534 
535     private static String getenvInChild(ProcessBuilder pb, String name) {
536         return javaChildOutput(pb, "System.getenv(String)", name);
537     }
538 
539     private static String pwdInChild(ProcessBuilder pb) {
540         return javaChildOutput(pb, "pwd");
541     }
542 
543     private static final String javaExe =
544         System.getProperty("java.home") +
545         File.separator + "bin" + File.separator + "java";
546 
547     private static final String classpath =
548         System.getProperty("java.class.path");
549 
550     private static final List<String> javaChildArgs =
551         Arrays.asList(new String[]
552             { javaExe, "-classpath", absolutifyPath(classpath),
553               "Basic$JavaChild"});
554 
555     private static void testEncoding(String encoding, String tested) {
556         try {
557             // If round trip conversion works, should be able to set env vars
558             // correctly in child.
559             if (new String(tested.getBytes()).equals(tested)) {
560                 out.println("Testing " + encoding + " environment values");
561                 ProcessBuilder pb = new ProcessBuilder();
562                 pb.environment().put("ASCIINAME",tested);
563                 equal(getenvInChild(pb,"ASCIINAME"), tested);
564             }
565         } catch (Throwable t) { unexpected(t); }
566     }
567 
568     static class Windows {
569         public static boolean is() { return is; }
570         private static final boolean is =
571             System.getProperty("os.name").startsWith("Windows");
572     }
573 
574     static class Unix {
575         public static boolean is() { return is; }
576         private static final boolean is =
577             (! Windows.is() &&
578              new File("/bin/sh").exists() &&
579              new File("/bin/true").exists() &&
580              new File("/bin/false").exists());
581     }
582 
583     static class UnicodeOS {
584         public static boolean is() { return is; }
585         private static final String osName = System.getProperty("os.name");
586         private static final boolean is =
587             // MacOS X would probably also qualify
588             osName.startsWith("Windows")   &&
589             ! osName.startsWith("Windows 9") &&
590             ! osName.equals("Windows Me");
591     }
592 
593     static class True {
594         public static int exitValue() { return 0; }
595     }
596 
597     private static class False {
598         public static int exitValue() { return exitValue; }
599         private static final int exitValue = exitValue0();
600         private static int exitValue0() {
601             // /bin/false returns an *unspecified* non-zero number.
602             try {
603                 if (! Unix.is())
604                     return -1;
605                 else {
606                     int rc = new ProcessBuilder("/bin/false")
607                         .start().waitFor();
608                     check(rc != 0);
609                     return rc;
610                 }
611             } catch (Throwable t) { unexpected(t); return -1; }
612         }
613     }
614 
615     static class EnglishUnix {
616         private final static Boolean is =
617             (! Windows.is() && isEnglish("LANG") && isEnglish("LC_ALL"));
618 
619         private static boolean isEnglish(String envvar) {
620             String val = getenv(envvar);
621             return (val == null) || val.matches("en.*");
622         }
623 
624         /** Returns true if we can expect English OS error strings */
625         static boolean is() { return is; }
626     }
627 
628     private static boolean matches(String str, String regex) {
629         return Pattern.compile(regex).matcher(str).find();
630     }
631 
632     private static String sortByLinesWindowsly(String text) {
633         String[] lines = text.split("\n");
634         Arrays.sort(lines, new WindowsComparator());
635         StringBuilder sb = new StringBuilder();
636         for (String line : lines)
637             sb.append(line).append("\n");
638         return sb.toString();
639     }
640 
641     private static void checkMapSanity(Map<String,String> map) {
642         try {
643             Set<String> keySet = map.keySet();
644             Collection<String> values = map.values();
645             Set<Map.Entry<String,String>> entrySet = map.entrySet();
646 
647             equal(entrySet.size(), keySet.size());
648             equal(entrySet.size(), values.size());
649 
650             StringBuilder s1 = new StringBuilder();
651             for (Map.Entry<String,String> e : entrySet)
652                 s1.append(e.getKey() + "=" + e.getValue() + "\n");
653 
654             StringBuilder s2 = new StringBuilder();
655             for (String var : keySet)
656                 s2.append(var + "=" + map.get(var) + "\n");
657 
658             equal(s1.toString(), s2.toString());
659 
660             Iterator<String> kIter = keySet.iterator();
661             Iterator<String> vIter = values.iterator();
662             Iterator<Map.Entry<String,String>> eIter = entrySet.iterator();
663 
664             while (eIter.hasNext()) {
665                 Map.Entry<String,String> entry = eIter.next();
666                 String key   = kIter.next();
667                 String value = vIter.next();
668                 check(entrySet.contains(entry));
669                 check(keySet.contains(key));
670                 check(values.contains(value));
671                 check(map.containsKey(key));
672                 check(map.containsValue(value));
673                 equal(entry.getKey(), key);
674                 equal(entry.getValue(), value);
675             }
676             check(! kIter.hasNext() &&
677                   ! vIter.hasNext());
678 
679         } catch (Throwable t) { unexpected(t); }
680     }
681 
682     private static void checkMapEquality(Map<String,String> map1,
683                                          Map<String,String> map2) {
684         try {
685             equal(map1.size(), map2.size());
686             equal(map1.isEmpty(), map2.isEmpty());
687             for (String key : map1.keySet()) {
688                 equal(map1.get(key), map2.get(key));
689                 check(map2.keySet().contains(key));
690             }
691             equal(map1, map2);
692             equal(map2, map1);
693             equal(map1.entrySet(), map2.entrySet());
694             equal(map2.entrySet(), map1.entrySet());
695             equal(map1.keySet(), map2.keySet());
696             equal(map2.keySet(), map1.keySet());
697 
698             equal(map1.hashCode(), map2.hashCode());
699             equal(map1.entrySet().hashCode(), map2.entrySet().hashCode());
700             equal(map1.keySet().hashCode(), map2.keySet().hashCode());
701         } catch (Throwable t) { unexpected(t); }
702     }
703 
704     static void checkRedirects(ProcessBuilder pb,
705                                Redirect in, Redirect out, Redirect err) {
706         equal(pb.redirectInput(),  in);
707         equal(pb.redirectOutput(), out);
708         equal(pb.redirectError(),  err);
709     }
710 
711     static void redirectIO(ProcessBuilder pb,
712                            Redirect in, Redirect out, Redirect err) {
713         pb.redirectInput(in);
714         pb.redirectOutput(out);
715         pb.redirectError(err);
716     }
717 
718     static void setFileContents(File file, String contents) {
719         try {
720             Writer w = new FileWriter(file);
721             w.write(contents);
722             w.close();
723         } catch (Throwable t) { unexpected(t); }
724     }
725 
726     static String fileContents(File file) {
727         try {
728             Reader r = new FileReader(file);
729             StringBuilder sb = new StringBuilder();
730             char[] buffer = new char[1024];
731             int n;
732             while ((n = r.read(buffer)) != -1)
733                 sb.append(buffer,0,n);
734             r.close();
735             return new String(sb);
736         } catch (Throwable t) { unexpected(t); return ""; }
737     }
738 
739     static void testIORedirection() throws Throwable {
740         final File ifile = new File("ifile");
741         final File ofile = new File("ofile");
742         final File efile = new File("efile");
743         ifile.delete();
744         ofile.delete();
745         efile.delete();
746 
747         //----------------------------------------------------------------
748         // Check mutual inequality of different types of Redirect
749         //----------------------------------------------------------------
750         Redirect[] redirects =
751             { PIPE,
752               INHERIT,
753               Redirect.from(ifile),
754               Redirect.to(ifile),
755               Redirect.appendTo(ifile),
756               Redirect.from(ofile),
757               Redirect.to(ofile),
758               Redirect.appendTo(ofile),
759             };
760         for (int i = 0; i < redirects.length; i++)
761             for (int j = 0; j < redirects.length; j++)
762                 equal(redirects[i].equals(redirects[j]), (i == j));
763 
764         //----------------------------------------------------------------
765         // Check basic properties of different types of Redirect
766         //----------------------------------------------------------------
767         equal(PIPE.type(), Redirect.Type.PIPE);
768         equal(PIPE.toString(), "PIPE");
769         equal(PIPE.file(), null);
770 
771         equal(INHERIT.type(), Redirect.Type.INHERIT);
772         equal(INHERIT.toString(), "INHERIT");
773         equal(INHERIT.file(), null);
774 
775         equal(Redirect.from(ifile).type(), Redirect.Type.READ);
776         equal(Redirect.from(ifile).toString(),
777               "redirect to read from file \"ifile\"");
778         equal(Redirect.from(ifile).file(), ifile);
779         equal(Redirect.from(ifile),
780               Redirect.from(ifile));
781         equal(Redirect.from(ifile).hashCode(),
782               Redirect.from(ifile).hashCode());
783 
784         equal(Redirect.to(ofile).type(), Redirect.Type.WRITE);
785         equal(Redirect.to(ofile).toString(),
786               "redirect to write to file \"ofile\"");
787         equal(Redirect.to(ofile).file(), ofile);
788         equal(Redirect.to(ofile),
789               Redirect.to(ofile));
790         equal(Redirect.to(ofile).hashCode(),
791               Redirect.to(ofile).hashCode());
792 
793         equal(Redirect.appendTo(ofile).type(), Redirect.Type.APPEND);
794         equal(Redirect.appendTo(efile).toString(),
795               "redirect to append to file \"efile\"");
796         equal(Redirect.appendTo(efile).file(), efile);
797         equal(Redirect.appendTo(efile),
798               Redirect.appendTo(efile));
799         equal(Redirect.appendTo(efile).hashCode(),
800               Redirect.appendTo(efile).hashCode());
801 
802         //----------------------------------------------------------------
803         // Check initial values of redirects
804         //----------------------------------------------------------------
805         List<String> childArgs = new ArrayList<String>(javaChildArgs);
806         childArgs.add("testIO");
807         final ProcessBuilder pb = new ProcessBuilder(childArgs);
808         checkRedirects(pb, PIPE, PIPE, PIPE);
809 
810         //----------------------------------------------------------------
811         // Check inheritIO
812         //----------------------------------------------------------------
813         pb.inheritIO();
814         checkRedirects(pb, INHERIT, INHERIT, INHERIT);
815 
816         //----------------------------------------------------------------
817         // Check setters and getters agree
818         //----------------------------------------------------------------
819         pb.redirectInput(ifile);
820         equal(pb.redirectInput().file(), ifile);
821         equal(pb.redirectInput(), Redirect.from(ifile));
822 
823         pb.redirectOutput(ofile);
824         equal(pb.redirectOutput().file(), ofile);
825         equal(pb.redirectOutput(), Redirect.to(ofile));
826 
827         pb.redirectError(efile);
828         equal(pb.redirectError().file(), efile);
829         equal(pb.redirectError(), Redirect.to(efile));
830 
831         THROWS(IllegalArgumentException.class,
832             new Fun(){void f() {
833                 pb.redirectInput(Redirect.to(ofile)); }},
834             new Fun(){void f() {
835                 pb.redirectInput(Redirect.appendTo(ofile)); }},
836             new Fun(){void f() {
837                 pb.redirectOutput(Redirect.from(ifile)); }},
838             new Fun(){void f() {
839                 pb.redirectError(Redirect.from(ifile)); }});
840 
841         THROWS(IOException.class,
842                // Input file does not exist
843                new Fun(){void f() throws Throwable { pb.start(); }});
844         setFileContents(ifile, "standard input");
845 
846         //----------------------------------------------------------------
847         // Writing to non-existent files
848         //----------------------------------------------------------------
849         {
850             ProcessResults r = run(pb);
851             equal(r.exitValue(), 0);
852             equal(fileContents(ofile), "standard output");
853             equal(fileContents(efile), "standard error");
854             equal(r.out(), "");
855             equal(r.err(), "");
856             ofile.delete();
857             efile.delete();
858         }
859 
860         //----------------------------------------------------------------
861         // Both redirectErrorStream + redirectError
862         //----------------------------------------------------------------
863         {
864             pb.redirectErrorStream(true);
865             ProcessResults r = run(pb);
866             equal(r.exitValue(), 0);
867             equal(fileContents(ofile),
868                   "standard error" + "standard output");
869             equal(fileContents(efile), "");
870             equal(r.out(), "");
871             equal(r.err(), "");
872             ofile.delete();
873             efile.delete();
874         }
875 
876         //----------------------------------------------------------------
877         // Appending to existing files
878         //----------------------------------------------------------------
879         {
880             setFileContents(ofile, "ofile-contents");
881             setFileContents(efile, "efile-contents");
882             pb.redirectOutput(Redirect.appendTo(ofile));
883             pb.redirectError(Redirect.appendTo(efile));
884             pb.redirectErrorStream(false);
885             ProcessResults r = run(pb);
886             equal(r.exitValue(), 0);
887             equal(fileContents(ofile),
888                   "ofile-contents" + "standard output");
889             equal(fileContents(efile),
890                   "efile-contents" + "standard error");
891             equal(r.out(), "");
892             equal(r.err(), "");
893             ofile.delete();
894             efile.delete();
895         }
896 
897         //----------------------------------------------------------------
898         // Replacing existing files
899         //----------------------------------------------------------------
900         {
901             setFileContents(ofile, "ofile-contents");
902             setFileContents(efile, "efile-contents");
903             pb.redirectOutput(ofile);
904             pb.redirectError(Redirect.to(efile));
905             ProcessResults r = run(pb);
906             equal(r.exitValue(), 0);
907             equal(fileContents(ofile), "standard output");
908             equal(fileContents(efile), "standard error");
909             equal(r.out(), "");
910             equal(r.err(), "");
911             ofile.delete();
912             efile.delete();
913         }
914 
915         //----------------------------------------------------------------
916         // Appending twice to the same file?
917         //----------------------------------------------------------------
918         {
919             setFileContents(ofile, "ofile-contents");
920             setFileContents(efile, "efile-contents");
921             Redirect appender = Redirect.appendTo(ofile);
922             pb.redirectOutput(appender);
923             pb.redirectError(appender);
924             ProcessResults r = run(pb);
925             equal(r.exitValue(), 0);
926             equal(fileContents(ofile),
927                   "ofile-contents" +
928                   "standard error" +
929                   "standard output");
930             equal(fileContents(efile), "efile-contents");
931             equal(r.out(), "");
932             equal(r.err(), "");
933             ifile.delete();
934             ofile.delete();
935             efile.delete();
936         }
937 
938         //----------------------------------------------------------------
939         // Testing INHERIT is harder.
940         // Note that this requires __FOUR__ nested JVMs involved in one test,
941         // if you count the harness JVM.
942         //----------------------------------------------------------------
943         {
944             redirectIO(pb, PIPE, PIPE, PIPE);
945             List<String> command = pb.command();
946             command.set(command.size() - 1, "testInheritIO");
947             Process p = pb.start();
948             new PrintStream(p.getOutputStream()).print("standard input");
949             p.getOutputStream().close();
950             ProcessResults r = run(p);
951             equal(r.exitValue(), 0);
952             equal(r.out(), "standard output");
953             equal(r.err(), "standard error");
954         }
955 
956         //----------------------------------------------------------------
957         // Test security implications of I/O redirection
958         //----------------------------------------------------------------
959 
960         // Read access to current directory is always granted;
961         // So create a tmpfile for input instead.
962         final File tmpFile = File.createTempFile("Basic", "tmp");
963         setFileContents(tmpFile, "standard input");
964 
965         final Policy policy = new Policy();
966         Policy.setPolicy(policy);
967         System.setSecurityManager(new SecurityManager());
968         try {
969             final Permission xPermission
970                 = new FilePermission("<<ALL FILES>>", "execute");
971             final Permission rxPermission
972                 = new FilePermission("<<ALL FILES>>", "read,execute");
973             final Permission wxPermission
974                 = new FilePermission("<<ALL FILES>>", "write,execute");
975             final Permission rwxPermission
976                 = new FilePermission("<<ALL FILES>>", "read,write,execute");
977 
978             THROWS(SecurityException.class,
979                new Fun() { void f() throws IOException {
980                    policy.setPermissions(xPermission);
981                    redirectIO(pb, from(tmpFile), PIPE, PIPE);
982                    pb.start();}},
983                new Fun() { void f() throws IOException {
984                    policy.setPermissions(rxPermission);
985                    redirectIO(pb, PIPE, to(ofile), PIPE);
986                    pb.start();}},
987                new Fun() { void f() throws IOException {
988                    policy.setPermissions(rxPermission);
989                    redirectIO(pb, PIPE, PIPE, to(efile));
990                    pb.start();}});
991 
992             {
993                 policy.setPermissions(rxPermission);
994                 redirectIO(pb, from(tmpFile), PIPE, PIPE);
995                 ProcessResults r = run(pb);
996                 equal(r.out(), "standard output");
997                 equal(r.err(), "standard error");
998             }
999 
1000             {
1001                 policy.setPermissions(wxPermission);
1002                 redirectIO(pb, PIPE, to(ofile), to(efile));
1003                 Process p = pb.start();
1004                 new PrintStream(p.getOutputStream()).print("standard input");
1005                 p.getOutputStream().close();
1006                 ProcessResults r = run(p);
1007                 policy.setPermissions(rwxPermission);
1008                 equal(fileContents(ofile), "standard output");
1009                 equal(fileContents(efile), "standard error");
1010             }
1011 
1012             {
1013                 policy.setPermissions(rwxPermission);
1014                 redirectIO(pb, from(tmpFile), to(ofile), to(efile));
1015                 ProcessResults r = run(pb);
1016                 policy.setPermissions(rwxPermission);
1017                 equal(fileContents(ofile), "standard output");
1018                 equal(fileContents(efile), "standard error");
1019             }
1020 
1021         } finally {
1022             policy.setPermissions(new RuntimePermission("setSecurityManager"));
1023             System.setSecurityManager(null);
1024             tmpFile.delete();
1025             ifile.delete();
1026             ofile.delete();
1027             efile.delete();
1028         }
1029     }
1030 
1031     private static void realMain(String[] args) throws Throwable {
1032         if (Windows.is())
1033             System.out.println("This appears to be a Windows system.");
1034         if (Unix.is())
1035             System.out.println("This appears to be a Unix system.");
1036         if (UnicodeOS.is())
1037             System.out.println("This appears to be a Unicode-based OS.");
1038 
1039         try { testIORedirection(); }
1040         catch (Throwable t) { unexpected(t); }
1041 
1042         //----------------------------------------------------------------
1043         // Basic tests for setting, replacing and deleting envvars
1044         //----------------------------------------------------------------
1045         try {
1046             ProcessBuilder pb = new ProcessBuilder();
1047             Map<String,String> environ = pb.environment();
1048 
1049             // New env var
1050             environ.put("QUUX", "BAR");
1051             equal(environ.get("QUUX"), "BAR");
1052             equal(getenvInChild(pb,"QUUX"), "BAR");
1053 
1054             // Modify env var
1055             environ.put("QUUX","bear");
1056             equal(environ.get("QUUX"), "bear");
1057             equal(getenvInChild(pb,"QUUX"), "bear");
1058             checkMapSanity(environ);
1059 
1060             // Remove env var
1061             environ.remove("QUUX");
1062             equal(environ.get("QUUX"), null);
1063             equal(getenvInChild(pb,"QUUX"), "null");
1064             checkMapSanity(environ);
1065 
1066             // Remove non-existent env var
1067             environ.remove("QUUX");
1068             equal(environ.get("QUUX"), null);
1069             equal(getenvInChild(pb,"QUUX"), "null");
1070             checkMapSanity(environ);
1071         } catch (Throwable t) { unexpected(t); }
1072 
1073         //----------------------------------------------------------------
1074         // Pass Empty environment to child
1075         //----------------------------------------------------------------
1076         try {
1077             ProcessBuilder pb = new ProcessBuilder();
1078             pb.environment().clear();
1079             String expected = Windows.is() ? "SystemRoot="+systemRoot+",": "";
1080             if (Windows.is()) {
1081                 pb.environment().put("SystemRoot", systemRoot);
1082             }
1083             equal(getenvInChild(pb), expected);
1084         } catch (Throwable t) { unexpected(t); }
1085 
1086         //----------------------------------------------------------------
1087         // System.getenv() is read-only.
1088         //----------------------------------------------------------------
1089         THROWS(UnsupportedOperationException.class,
1090             new Fun(){void f(){ getenv().put("FOO","BAR");}},
1091             new Fun(){void f(){ getenv().remove("PATH");}},
1092             new Fun(){void f(){ getenv().keySet().remove("PATH");}},
1093             new Fun(){void f(){ getenv().values().remove("someValue");}});
1094 
1095         try {
1096             Collection<Map.Entry<String,String>> c = getenv().entrySet();
1097             if (! c.isEmpty())
1098                 try {
1099                     c.iterator().next().setValue("foo");
1100                     fail("Expected UnsupportedOperationException not thrown");
1101                 } catch (UnsupportedOperationException e) {} // OK
1102         } catch (Throwable t) { unexpected(t); }
1103 
1104         //----------------------------------------------------------------
1105         // System.getenv() always returns the same object in our implementation.
1106         //----------------------------------------------------------------
1107         try {
1108             check(System.getenv() == System.getenv());
1109         } catch (Throwable t) { unexpected(t); }
1110 
1111         //----------------------------------------------------------------
1112         // You can't create an env var name containing "=",
1113         // or an env var name or value containing NUL.
1114         //----------------------------------------------------------------
1115         {
1116             final Map<String,String> m = new ProcessBuilder().environment();
1117             THROWS(IllegalArgumentException.class,
1118                 new Fun(){void f(){ m.put("FOO=","BAR");}},
1119                 new Fun(){void f(){ m.put("FOO\u0000","BAR");}},
1120                 new Fun(){void f(){ m.put("FOO","BAR\u0000");}});
1121         }
1122 
1123         //----------------------------------------------------------------
1124         // Commands must never be null.
1125         //----------------------------------------------------------------
1126         THROWS(NullPointerException.class,
1127                new Fun(){void f(){
1128                    new ProcessBuilder((List<String>)null);}},
1129                new Fun(){void f(){
1130                    new ProcessBuilder().command((List<String>)null);}});
1131 
1132         //----------------------------------------------------------------
1133         // Put in a command; get the same one back out.
1134         //----------------------------------------------------------------
1135         try {
1136             List<String> command = new ArrayList<String>();
1137             ProcessBuilder pb = new ProcessBuilder(command);
1138             check(pb.command() == command);
1139             List<String> command2 = new ArrayList<String>(2);
1140             command2.add("foo");
1141             command2.add("bar");
1142             pb.command(command2);
1143             check(pb.command() == command2);
1144             pb.command("foo", "bar");
1145             check(pb.command() != command2 && pb.command().equals(command2));
1146             pb.command(command2);
1147             command2.add("baz");
1148             equal(pb.command().get(2), "baz");
1149         } catch (Throwable t) { unexpected(t); }
1150 
1151         //----------------------------------------------------------------
1152         // Commands must contain at least one element.
1153         //----------------------------------------------------------------
1154         THROWS(IndexOutOfBoundsException.class,
1155             new Fun() { void f() throws IOException {
1156                 new ProcessBuilder().start();}},
1157             new Fun() { void f() throws IOException {
1158                 new ProcessBuilder(new ArrayList<String>()).start();}},
1159             new Fun() { void f() throws IOException {
1160                 Runtime.getRuntime().exec(new String[]{});}});
1161 
1162         //----------------------------------------------------------------
1163         // Commands must not contain null elements at start() time.
1164         //----------------------------------------------------------------
1165         THROWS(NullPointerException.class,
1166             new Fun() { void f() throws IOException {
1167                 new ProcessBuilder("foo",null,"bar").start();}},
1168             new Fun() { void f() throws IOException {
1169                 new ProcessBuilder((String)null).start();}},
1170             new Fun() { void f() throws IOException {
1171                 new ProcessBuilder(new String[]{null}).start();}},
1172             new Fun() { void f() throws IOException {
1173                 new ProcessBuilder(new String[]{"foo",null,"bar"}).start();}});
1174 
1175         //----------------------------------------------------------------
1176         // Command lists are growable.
1177         //----------------------------------------------------------------
1178         try {
1179             new ProcessBuilder().command().add("foo");
1180             new ProcessBuilder("bar").command().add("foo");
1181             new ProcessBuilder(new String[]{"1","2"}).command().add("3");
1182         } catch (Throwable t) { unexpected(t); }
1183 
1184         //----------------------------------------------------------------
1185         // Nulls in environment updates generate NullPointerException
1186         //----------------------------------------------------------------
1187         try {
1188             final Map<String,String> env = new ProcessBuilder().environment();
1189             THROWS(NullPointerException.class,
1190                 new Fun(){void f(){ env.put("foo",null);}},
1191                 new Fun(){void f(){ env.put(null,"foo");}},
1192                 new Fun(){void f(){ env.remove(null);}},
1193                 new Fun(){void f(){
1194                     for (Map.Entry<String,String> e : env.entrySet())
1195                         e.setValue(null);}},
1196                 new Fun() { void f() throws IOException {
1197                     Runtime.getRuntime().exec(new String[]{"foo"},
1198                                               new String[]{null});}});
1199         } catch (Throwable t) { unexpected(t); }
1200 
1201         //----------------------------------------------------------------
1202         // Non-String types in environment updates generate ClassCastException
1203         //----------------------------------------------------------------
1204         try {
1205             final Map<String,String> env = new ProcessBuilder().environment();
1206             THROWS(ClassCastException.class,
1207                 new Fun(){void f(){ env.remove(TRUE);}},
1208                 new Fun(){void f(){ env.keySet().remove(TRUE);}},
1209                 new Fun(){void f(){ env.values().remove(TRUE);}},
1210                 new Fun(){void f(){ env.entrySet().remove(TRUE);}});
1211         } catch (Throwable t) { unexpected(t); }
1212 
1213         //----------------------------------------------------------------
1214         // Check query operations on environment maps
1215         //----------------------------------------------------------------
1216         try {
1217             List<Map<String,String>> envs =
1218                 new ArrayList<Map<String,String>>(2);
1219             envs.add(System.getenv());
1220             envs.add(new ProcessBuilder().environment());
1221             for (final Map<String,String> env : envs) {
1222                 //----------------------------------------------------------------
1223                 // Nulls in environment queries are forbidden.
1224                 //----------------------------------------------------------------
1225                 THROWS(NullPointerException.class,
1226                     new Fun(){void f(){ getenv(null);}},
1227                     new Fun(){void f(){ env.get(null);}},
1228                     new Fun(){void f(){ env.containsKey(null);}},
1229                     new Fun(){void f(){ env.containsValue(null);}},
1230                     new Fun(){void f(){ env.keySet().contains(null);}},
1231                     new Fun(){void f(){ env.values().contains(null);}});
1232 
1233                 //----------------------------------------------------------------
1234                 // Non-String types in environment queries are forbidden.
1235                 //----------------------------------------------------------------
1236                 THROWS(ClassCastException.class,
1237                     new Fun(){void f(){ env.get(TRUE);}},
1238                     new Fun(){void f(){ env.containsKey(TRUE);}},
1239                     new Fun(){void f(){ env.containsValue(TRUE);}},
1240                     new Fun(){void f(){ env.keySet().contains(TRUE);}},
1241                     new Fun(){void f(){ env.values().contains(TRUE);}});
1242 
1243                 //----------------------------------------------------------------
1244                 // Illegal String values in environment queries are (grumble) OK
1245                 //----------------------------------------------------------------
1246                 equal(env.get("\u0000"), null);
1247                 check(! env.containsKey("\u0000"));
1248                 check(! env.containsValue("\u0000"));
1249                 check(! env.keySet().contains("\u0000"));
1250                 check(! env.values().contains("\u0000"));
1251             }
1252 
1253         } catch (Throwable t) { unexpected(t); }
1254 
1255         try {
1256             final Set<Map.Entry<String,String>> entrySet =
1257                 new ProcessBuilder().environment().entrySet();
1258             THROWS(NullPointerException.class,
1259                    new Fun(){void f(){ entrySet.contains(null);}});
1260             THROWS(ClassCastException.class,
1261                 new Fun(){void f(){ entrySet.contains(TRUE);}},
1262                 new Fun(){void f(){
1263                     entrySet.contains(
1264                         new SimpleImmutableEntry<Boolean,String>(TRUE,""));}});
1265 
1266             check(! entrySet.contains
1267                   (new SimpleImmutableEntry<String,String>("", "")));
1268         } catch (Throwable t) { unexpected(t); }
1269 
1270         //----------------------------------------------------------------
1271         // Put in a directory; get the same one back out.
1272         //----------------------------------------------------------------
1273         try {
1274             ProcessBuilder pb = new ProcessBuilder();
1275             File foo = new File("foo");
1276             equal(pb.directory(), null);
1277             equal(pb.directory(foo).directory(), foo);
1278             equal(pb.directory(null).directory(), null);
1279         } catch (Throwable t) { unexpected(t); }
1280 
1281         //----------------------------------------------------------------
1282         // If round-trip conversion works, check envvar pass-through to child
1283         //----------------------------------------------------------------
1284         try {
1285             testEncoding("ASCII",   "xyzzy");
1286             testEncoding("Latin1",  "\u00f1\u00e1");
1287             testEncoding("Unicode", "\u22f1\u11e1");
1288         } catch (Throwable t) { unexpected(t); }
1289 
1290         //----------------------------------------------------------------
1291         // A surprisingly large number of ways to delete an environment var.
1292         //----------------------------------------------------------------
1293         testVariableDeleter(new EnvironmentFrobber() {
1294                 public void doIt(Map<String,String> environ) {
1295                     environ.remove("Foo");}});
1296 
1297         testVariableDeleter(new EnvironmentFrobber() {
1298                 public void doIt(Map<String,String> environ) {
1299                     environ.keySet().remove("Foo");}});
1300 
1301         testVariableDeleter(new EnvironmentFrobber() {
1302                 public void doIt(Map<String,String> environ) {
1303                     environ.values().remove("BAAR");}});
1304 
1305         testVariableDeleter(new EnvironmentFrobber() {
1306                 public void doIt(Map<String,String> environ) {
1307                     // Legally fabricate a ProcessEnvironment.StringEntry,
1308                     // even though it's private.
1309                     Map<String,String> environ2
1310                         = new ProcessBuilder().environment();
1311                     environ2.clear();
1312                     environ2.put("Foo","BAAR");
1313                     // Subtlety alert.
1314                     Map.Entry<String,String> e
1315                         = environ2.entrySet().iterator().next();
1316                     environ.entrySet().remove(e);}});
1317 
1318         testVariableDeleter(new EnvironmentFrobber() {
1319                 public void doIt(Map<String,String> environ) {
1320                     Map.Entry<String,String> victim = null;
1321                     for (Map.Entry<String,String> e : environ.entrySet())
1322                         if (e.getKey().equals("Foo"))
1323                             victim = e;
1324                     if (victim != null)
1325                         environ.entrySet().remove(victim);}});
1326 
1327         testVariableDeleter(new EnvironmentFrobber() {
1328                 public void doIt(Map<String,String> environ) {
1329                     Iterator<String> it = environ.keySet().iterator();
1330                     while (it.hasNext()) {
1331                         String val = it.next();
1332                         if (val.equals("Foo"))
1333                             it.remove();}}});
1334 
1335         testVariableDeleter(new EnvironmentFrobber() {
1336                 public void doIt(Map<String,String> environ) {
1337                     Iterator<Map.Entry<String,String>> it
1338                         = environ.entrySet().iterator();
1339                     while (it.hasNext()) {
1340                         Map.Entry<String,String> e = it.next();
1341                         if (e.getKey().equals("Foo"))
1342                             it.remove();}}});
1343 
1344         testVariableDeleter(new EnvironmentFrobber() {
1345                 public void doIt(Map<String,String> environ) {
1346                     Iterator<String> it = environ.values().iterator();
1347                     while (it.hasNext()) {
1348                         String val = it.next();
1349                         if (val.equals("BAAR"))
1350                             it.remove();}}});
1351 
1352         //----------------------------------------------------------------
1353         // A surprisingly small number of ways to add an environment var.
1354         //----------------------------------------------------------------
1355         testVariableAdder(new EnvironmentFrobber() {
1356                 public void doIt(Map<String,String> environ) {
1357                     environ.put("Foo","Bahrein");}});
1358 
1359         //----------------------------------------------------------------
1360         // A few ways to modify an environment var.
1361         //----------------------------------------------------------------
1362         testVariableModifier(new EnvironmentFrobber() {
1363                 public void doIt(Map<String,String> environ) {
1364                     environ.put("Foo","NewValue");}});
1365 
1366         testVariableModifier(new EnvironmentFrobber() {
1367                 public void doIt(Map<String,String> environ) {
1368                     for (Map.Entry<String,String> e : environ.entrySet())
1369                         if (e.getKey().equals("Foo"))
1370                             e.setValue("NewValue");}});
1371 
1372         //----------------------------------------------------------------
1373         // Fiddle with environment sizes
1374         //----------------------------------------------------------------
1375         try {
1376             Map<String,String> environ = new ProcessBuilder().environment();
1377             int size = environ.size();
1378             checkSizes(environ, size);
1379 
1380             environ.put("UnLiKeLYeNVIROmtNam", "someVal");
1381             checkSizes(environ, size+1);
1382 
1383             // Check for environment independence
1384             new ProcessBuilder().environment().clear();
1385 
1386             environ.put("UnLiKeLYeNVIROmtNam", "someOtherVal");
1387             checkSizes(environ, size+1);
1388 
1389             environ.remove("UnLiKeLYeNVIROmtNam");
1390             checkSizes(environ, size);
1391 
1392             environ.clear();
1393             checkSizes(environ, 0);
1394 
1395             environ.clear();
1396             checkSizes(environ, 0);
1397 
1398             environ = new ProcessBuilder().environment();
1399             environ.keySet().clear();
1400             checkSizes(environ, 0);
1401 
1402             environ = new ProcessBuilder().environment();
1403             environ.entrySet().clear();
1404             checkSizes(environ, 0);
1405 
1406             environ = new ProcessBuilder().environment();
1407             environ.values().clear();
1408             checkSizes(environ, 0);
1409         } catch (Throwable t) { unexpected(t); }
1410 
1411         //----------------------------------------------------------------
1412         // Check that various map invariants hold
1413         //----------------------------------------------------------------
1414         checkMapSanity(new ProcessBuilder().environment());
1415         checkMapSanity(System.getenv());
1416         checkMapEquality(new ProcessBuilder().environment(),
1417                          new ProcessBuilder().environment());
1418 
1419 
1420         //----------------------------------------------------------------
1421         // Check effects on external "env" command.
1422         //----------------------------------------------------------------
1423         try {
1424             Set<String> env1 = new HashSet<String>
1425                 (Arrays.asList(nativeEnv((String[])null).split("\n")));
1426 
1427             ProcessBuilder pb = new ProcessBuilder();
1428             pb.environment().put("QwErTyUiOp","AsDfGhJk");
1429 
1430             Set<String> env2 = new HashSet<String>
1431                 (Arrays.asList(nativeEnv(pb).split("\n")));
1432 
1433             check(env2.size() == env1.size() + 1);
1434             env1.add("QwErTyUiOp=AsDfGhJk");
1435             check(env1.equals(env2));
1436         } catch (Throwable t) { unexpected(t); }
1437 
1438         //----------------------------------------------------------------
1439         // Test Runtime.exec(...envp...)
1440         // Check for sort order of environment variables on Windows.
1441         //----------------------------------------------------------------
1442         try {
1443             String systemRoot = "SystemRoot=" + System.getenv("SystemRoot");
1444             // '+' < 'A' < 'Z' < '_' < 'a' < 'z' < '~'
1445             String[]envp = {"FOO=BAR","BAZ=GORP","QUUX=",
1446                             "+=+", "_=_", "~=~", systemRoot};
1447             String output = nativeEnv(envp);
1448             String expected = "+=+\nBAZ=GORP\nFOO=BAR\nQUUX=\n"+systemRoot+"\n_=_\n~=~\n";
1449             // On Windows, Java must keep the environment sorted.
1450             // Order is random on Unix, so this test does the sort.
1451             if (! Windows.is())
1452                 output = sortByLinesWindowsly(output);
1453             equal(output, expected);
1454         } catch (Throwable t) { unexpected(t); }
1455 
1456         //----------------------------------------------------------------
1457         // Test Runtime.exec(...envp...)
1458         // and check SystemRoot gets set automatically on Windows
1459         //----------------------------------------------------------------
1460         try {
1461             if (Windows.is()) {
1462                 String systemRoot = "SystemRoot=" + System.getenv("SystemRoot");
1463                 String[]envp = {"FOO=BAR","BAZ=GORP","QUUX=",
1464                                 "+=+", "_=_", "~=~"};
1465                 String output = nativeEnv(envp);
1466                 String expected = "+=+\nBAZ=GORP\nFOO=BAR\nQUUX=\n"+systemRoot+"\n_=_\n~=~\n";
1467                 equal(output, expected);
1468             }
1469         } catch (Throwable t) { unexpected(t); }
1470 
1471         //----------------------------------------------------------------
1472         // System.getenv() must be consistent with System.getenv(String)
1473         //----------------------------------------------------------------
1474         try {
1475             for (Map.Entry<String,String> e : getenv().entrySet())
1476                 equal(getenv(e.getKey()), e.getValue());
1477         } catch (Throwable t) { unexpected(t); }
1478 
1479         //----------------------------------------------------------------
1480         // Fiddle with working directory in child
1481         //----------------------------------------------------------------
1482         try {
1483             String canonicalUserDir =
1484                 new File(System.getProperty("user.dir")).getCanonicalPath();
1485             String[] sdirs = new String[]
1486                 {".", "..", "/", "/bin",
1487                  "C:", "c:", "C:/", "c:\\", "\\", "\\bin",
1488                  "c:\\windows  ", "c:\\Program Files", "c:\\Program Files\\" };
1489             for (String sdir : sdirs) {
1490                 File dir = new File(sdir);
1491                 if (! (dir.isDirectory() && dir.exists()))
1492                     continue;
1493                 out.println("Testing directory " + dir);
1494                 //dir = new File(dir.getCanonicalPath());
1495 
1496                 ProcessBuilder pb = new ProcessBuilder();
1497                 equal(pb.directory(), null);
1498                 equal(pwdInChild(pb), canonicalUserDir);
1499 
1500                 pb.directory(dir);
1501                 equal(pb.directory(), dir);
1502                 equal(pwdInChild(pb), dir.getCanonicalPath());
1503 
1504                 pb.directory(null);
1505                 equal(pb.directory(), null);
1506                 equal(pwdInChild(pb), canonicalUserDir);
1507 
1508                 pb.directory(dir);
1509             }
1510         } catch (Throwable t) { unexpected(t); }
1511 
1512         //----------------------------------------------------------------
1513         // Working directory with Unicode in child
1514         //----------------------------------------------------------------
1515         try {
1516             if (UnicodeOS.is()) {
1517                 File dir = new File(System.getProperty("test.dir", "."),
1518                                     "ProcessBuilderDir\u4e00\u4e02");
1519                 try {
1520                     if (!dir.exists())
1521                         dir.mkdir();
1522                     out.println("Testing Unicode directory:" + dir);
1523                     ProcessBuilder pb = new ProcessBuilder();
1524                     pb.directory(dir);
1525                     equal(pwdInChild(pb), dir.getCanonicalPath());
1526                 } finally {
1527                     if (dir.exists())
1528                         dir.delete();
1529                 }
1530             }
1531         } catch (Throwable t) { unexpected(t); }
1532 
1533         //----------------------------------------------------------------
1534         // OOME in child allocating maximally sized array
1535         // Test for hotspot/jvmti bug 6850957
1536         //----------------------------------------------------------------
1537         try {
1538             List<String> list = new ArrayList<String>(javaChildArgs);
1539             list.add(1, String.format("-XX:OnOutOfMemoryError=%s -version",
1540                                       javaExe));
1541             list.add("ArrayOOME");
1542             ProcessResults r = run(new ProcessBuilder(list));
1543             check(r.out().contains("java.lang.OutOfMemoryError:"));
1544             check(r.out().contains(javaExe));
1545             check(r.err().contains(System.getProperty("java.version")));
1546             equal(r.exitValue(), 1);
1547         } catch (Throwable t) { unexpected(t); }
1548 
1549         //----------------------------------------------------------------
1550         // Windows has tricky semi-case-insensitive semantics
1551         //----------------------------------------------------------------
1552         if (Windows.is())
1553             try {
1554                 out.println("Running case insensitve variable tests");
1555                 for (String[] namePair :
1556                          new String[][]
1557                     { new String[]{"PATH","PaTh"},
1558                       new String[]{"home","HOME"},
1559                       new String[]{"SYSTEMROOT","SystemRoot"}}) {
1560                     check((getenv(namePair[0]) == null &&
1561                            getenv(namePair[1]) == null)
1562                           ||
1563                           getenv(namePair[0]).equals(getenv(namePair[1])),
1564                           "Windows environment variables are not case insensitive");
1565                 }
1566             } catch (Throwable t) { unexpected(t); }
1567 
1568         //----------------------------------------------------------------
1569         // Test proper Unicode child environment transfer
1570         //----------------------------------------------------------------
1571         if (UnicodeOS.is())
1572             try {
1573                 ProcessBuilder pb = new ProcessBuilder();
1574                 pb.environment().put("\u1234","\u5678");
1575                 pb.environment().remove("PATH");
1576                 equal(getenvInChild1234(pb), "\u5678");
1577             } catch (Throwable t) { unexpected(t); }
1578 
1579 
1580         //----------------------------------------------------------------
1581         // Test Runtime.exec(...envp...) with envstrings with initial `='
1582         //----------------------------------------------------------------
1583         try {
1584             List<String> childArgs = new ArrayList<String>(javaChildArgs);
1585             childArgs.add("System.getenv()");
1586             String[] cmdp = childArgs.toArray(new String[childArgs.size()]);
1587             String[] envp;
1588             String[] envpWin = {"=ExitValue=3", "=C:=\\", "SystemRoot="+systemRoot};
1589             String[] envpOth = {"=ExitValue=3", "=C:=\\"};
1590             if (Windows.is()) {
1591                 envp = envpWin;
1592             } else {
1593                 envp = envpOth;
1594             }
1595             Process p = Runtime.getRuntime().exec(cmdp, envp);
1596             String expected = Windows.is() ? "=C:=\\,SystemRoot="+systemRoot+",=ExitValue=3," : "=C:=\\,";
1597             equal(commandOutput(p), expected);
1598             if (Windows.is()) {
1599                 ProcessBuilder pb = new ProcessBuilder(childArgs);
1600                 pb.environment().clear();
1601                 pb.environment().put("SystemRoot", systemRoot);
1602                 pb.environment().put("=ExitValue", "3");
1603                 pb.environment().put("=C:", "\\");
1604                 equal(commandOutput(pb), expected);
1605             }
1606         } catch (Throwable t) { unexpected(t); }
1607 
1608         //----------------------------------------------------------------
1609         // Test Runtime.exec(...envp...) with envstrings without any `='
1610         //----------------------------------------------------------------
1611         try {
1612             String[] cmdp = {"echo"};
1613             String[] envp = {"Hello", "World"}; // Yuck!
1614             Process p = Runtime.getRuntime().exec(cmdp, envp);
1615             equal(commandOutput(p), "\n");
1616         } catch (Throwable t) { unexpected(t); }
1617 
1618         //----------------------------------------------------------------
1619         // Test Runtime.exec(...envp...) with envstrings containing NULs
1620         //----------------------------------------------------------------
1621         try {
1622             List<String> childArgs = new ArrayList<String>(javaChildArgs);
1623             childArgs.add("System.getenv()");
1624             String[] cmdp = childArgs.toArray(new String[childArgs.size()]);
1625             String[] envpWin = {"SystemRoot="+systemRoot, "LC_ALL=C\u0000\u0000", // Yuck!
1626                              "FO\u0000=B\u0000R"};
1627             String[] envpOth = {"LC_ALL=C\u0000\u0000", // Yuck!
1628                              "FO\u0000=B\u0000R"};
1629             String[] envp;
1630             if (Windows.is()) {
1631                 envp = envpWin;
1632             } else {
1633                 envp = envpOth;
1634             }
1635             Process p = Runtime.getRuntime().exec(cmdp, envp);
1636             check(commandOutput(p).equals(Windows.is() ? "SystemRoot="+systemRoot+",LC_ALL=C," : "LC_ALL=C,"),
1637                   "Incorrect handling of envstrings containing NULs");
1638         } catch (Throwable t) { unexpected(t); }
1639 
1640         //----------------------------------------------------------------
1641         // Test the redirectErrorStream property
1642         //----------------------------------------------------------------
1643         try {
1644             ProcessBuilder pb = new ProcessBuilder();
1645             equal(pb.redirectErrorStream(), false);
1646             equal(pb.redirectErrorStream(true), pb);
1647             equal(pb.redirectErrorStream(), true);
1648             equal(pb.redirectErrorStream(false), pb);
1649             equal(pb.redirectErrorStream(), false);
1650         } catch (Throwable t) { unexpected(t); }
1651 
1652         try {
1653             List<String> childArgs = new ArrayList<String>(javaChildArgs);
1654             childArgs.add("OutErr");
1655             ProcessBuilder pb = new ProcessBuilder(childArgs);
1656             {
1657                 ProcessResults r = run(pb);
1658                 equal(r.out(), "outout");
1659                 equal(r.err(), "errerr");
1660             }
1661             {
1662                 pb.redirectErrorStream(true);
1663                 ProcessResults r = run(pb);
1664                 equal(r.out(), "outerrouterr");
1665                 equal(r.err(), "");
1666             }
1667         } catch (Throwable t) { unexpected(t); }
1668 
1669         if (Unix.is()) {
1670             //----------------------------------------------------------------
1671             // We can find true and false when PATH is null
1672             //----------------------------------------------------------------
1673             try {
1674                 List<String> childArgs = new ArrayList<String>(javaChildArgs);
1675                 childArgs.add("null PATH");
1676                 ProcessBuilder pb = new ProcessBuilder(childArgs);
1677                 pb.environment().remove("PATH");
1678                 ProcessResults r = run(pb);
1679                 equal(r.out(), "");
1680                 equal(r.err(), "");
1681                 equal(r.exitValue(), 0);
1682             } catch (Throwable t) { unexpected(t); }
1683 
1684             //----------------------------------------------------------------
1685             // PATH search algorithm on Unix
1686             //----------------------------------------------------------------
1687             try {
1688                 List<String> childArgs = new ArrayList<String>(javaChildArgs);
1689                 childArgs.add("PATH search algorithm");
1690                 ProcessBuilder pb = new ProcessBuilder(childArgs);
1691                 pb.environment().put("PATH", "dir1:dir2:");
1692                 ProcessResults r = run(pb);
1693                 equal(r.out(), "");
1694                 equal(r.err(), "");
1695                 equal(r.exitValue(), True.exitValue());
1696             } catch (Throwable t) { unexpected(t); }
1697 
1698             //----------------------------------------------------------------
1699             // Parent's, not child's PATH is used
1700             //----------------------------------------------------------------
1701             try {
1702                 new File("suBdiR").mkdirs();
1703                 copy("/bin/true", "suBdiR/unliKely");
1704                 final ProcessBuilder pb =
1705                     new ProcessBuilder(new String[]{"unliKely"});
1706                 pb.environment().put("PATH", "suBdiR");
1707                 THROWS(IOException.class,
1708                        new Fun() {void f() throws Throwable {pb.start();}});
1709             } catch (Throwable t) { unexpected(t);
1710             } finally {
1711                 new File("suBdiR/unliKely").delete();
1712                 new File("suBdiR").delete();
1713             }
1714         }
1715 
1716         //----------------------------------------------------------------
1717         // Attempt to start bogus program ""
1718         //----------------------------------------------------------------
1719         try {
1720             new ProcessBuilder("").start();
1721             fail("Expected IOException not thrown");
1722         } catch (IOException e) {
1723             String m = e.getMessage();
1724             if (EnglishUnix.is() &&
1725                 ! matches(m, "No such file or directory"))
1726                 unexpected(e);
1727         } catch (Throwable t) { unexpected(t); }
1728 
1729         //----------------------------------------------------------------
1730         // Check that attempt to execute program name with funny
1731         // characters throws an exception containing those characters.
1732         //----------------------------------------------------------------
1733         for (String programName : new String[] {"\u00f0", "\u01f0"})
1734             try {
1735                 new ProcessBuilder(programName).start();
1736                 fail("Expected IOException not thrown");
1737             } catch (IOException e) {
1738                 String m = e.getMessage();
1739                 Pattern p = Pattern.compile(programName);
1740                 if (! matches(m, programName)
1741                     || (EnglishUnix.is()
1742                         && ! matches(m, "No such file or directory")))
1743                     unexpected(e);
1744             } catch (Throwable t) { unexpected(t); }
1745 
1746         //----------------------------------------------------------------
1747         // Attempt to start process in nonexistent directory fails.
1748         //----------------------------------------------------------------
1749         try {
1750             new ProcessBuilder("echo")
1751                 .directory(new File("UnLiKeLY"))
1752                 .start();
1753             fail("Expected IOException not thrown");
1754         } catch (IOException e) {
1755             String m = e.getMessage();
1756             if (! matches(m, "in directory")
1757                 || (EnglishUnix.is() &&
1758                     ! matches(m, "No such file or directory")))
1759                 unexpected(e);
1760         } catch (Throwable t) { unexpected(t); }
1761 
1762         //----------------------------------------------------------------
1763         // Attempt to write 4095 bytes to the pipe buffer without a
1764         // reader to drain it would deadlock, if not for the fact that
1765         // interprocess pipe buffers are at least 4096 bytes.
1766         //
1767         // Also, check that available reports all the bytes expected
1768         // in the pipe buffer, and that I/O operations do the expected
1769         // things.
1770         //----------------------------------------------------------------
1771         try {
1772             List<String> childArgs = new ArrayList<String>(javaChildArgs);
1773             childArgs.add("print4095");
1774             final int SIZE = 4095;
1775             final Process p = new ProcessBuilder(childArgs).start();
1776             print4095(p.getOutputStream(), (byte) '!'); // Might hang!
1777             p.waitFor();                                // Might hang!
1778             equal(SIZE, p.getInputStream().available());
1779             equal(SIZE, p.getErrorStream().available());
1780             THROWS(IOException.class,
1781                    new Fun(){void f() throws IOException {
1782                        p.getOutputStream().write((byte) '!');
1783                        p.getOutputStream().flush();
1784                        }});
1785 
1786             final byte[] bytes = new byte[SIZE + 1];
1787             equal(SIZE, p.getInputStream().read(bytes));
1788             for (int i = 0; i < SIZE; i++)
1789                 equal((byte) '!', bytes[i]);
1790             equal((byte) 0, bytes[SIZE]);
1791 
1792             equal(SIZE, p.getErrorStream().read(bytes));
1793             for (int i = 0; i < SIZE; i++)
1794                 equal((byte) 'E', bytes[i]);
1795             equal((byte) 0, bytes[SIZE]);
1796 
1797             equal(0, p.getInputStream().available());
1798             equal(0, p.getErrorStream().available());
1799             equal(-1, p.getErrorStream().read());
1800             equal(-1, p.getInputStream().read());
1801 
1802             equal(p.exitValue(), 5);
1803 
1804             p.getInputStream().close();
1805             p.getErrorStream().close();
1806             p.getOutputStream().close();
1807 
1808             InputStream[] streams = { p.getInputStream(), p.getErrorStream() };
1809             for (final InputStream in : streams) {
1810                 Fun[] ops = {
1811                     new Fun(){void f() throws IOException {
1812                         in.read(); }},
1813                     new Fun(){void f() throws IOException {
1814                         in.read(bytes); }},
1815                     new Fun(){void f() throws IOException {
1816                         in.available(); }}
1817                 };
1818                 for (Fun op : ops) {
1819                     try {
1820                         op.f();
1821                         fail();
1822                     } catch (IOException expected) {
1823                         check(expected.getMessage()
1824                               .matches("[Ss]tream [Cc]losed"));
1825                     }
1826                 }
1827             }
1828         } catch (Throwable t) { unexpected(t); }
1829 
1830         //----------------------------------------------------------------
1831         // Check that reads which are pending when Process.destroy is
1832         // called, get EOF, not IOException("Stream closed").
1833         //----------------------------------------------------------------
1834         try {
1835             final int cases = 4;
1836             for (int i = 0; i < cases; i++) {
1837                 final int action = i;
1838                 List<String> childArgs = new ArrayList<String>(javaChildArgs);
1839                 childArgs.add("sleep");
1840                 final byte[] bytes = new byte[10];
1841                 final Process p = new ProcessBuilder(childArgs).start();
1842                 final CountDownLatch latch = new CountDownLatch(1);
1843                 final Thread thread = new Thread() {
1844                     public void run() {
1845                         try {
1846                             latch.countDown();
1847                             int r;
1848                             switch (action) {
1849                             case 0: r = p.getInputStream().read(); break;
1850                             case 1: r = p.getErrorStream().read(); break;
1851                             case 2: r = p.getInputStream().read(bytes); break;
1852                             case 3: r = p.getErrorStream().read(bytes); break;
1853                             default: throw new Error();
1854                             }
1855                             equal(-1, r);
1856                         } catch (Throwable t) { unexpected(t); }}};
1857 
1858                 thread.start();
1859                 latch.await();
1860                 Thread.sleep(10);
1861                 p.destroy();
1862                 thread.join();
1863             }
1864         } catch (Throwable t) { unexpected(t); }
1865 
1866         //----------------------------------------------------------------
1867         // Check that subprocesses which create subprocesses of their
1868         // own do not cause parent to hang waiting for file
1869         // descriptors to be closed.
1870         //----------------------------------------------------------------
1871         try {
1872             if (Unix.is()
1873                 && new File("/bin/bash").exists()
1874                 && new File("/bin/sleep").exists()) {
1875                 final String[] cmd = { "/bin/bash", "-c", "(/bin/sleep 6666)" };
1876                 final ProcessBuilder pb = new ProcessBuilder(cmd);
1877                 final Process p = pb.start();
1878                 final InputStream stdout = p.getInputStream();
1879                 final InputStream stderr = p.getErrorStream();
1880                 final OutputStream stdin = p.getOutputStream();
1881                 final Thread reader = new Thread() {
1882                     public void run() {
1883                         try { stdout.read(); }
1884                         catch (IOException e) {
1885                             // Check that reader failed because stream was
1886                             // asynchronously closed.
1887                             // e.printStackTrace();
1888                             if (EnglishUnix.is() &&
1889                                 ! (e.getMessage().matches(".*Bad file.*")))
1890                                 unexpected(e);
1891                         }
1892                         catch (Throwable t) { unexpected(t); }}};
1893                 reader.setDaemon(true);
1894                 reader.start();
1895                 Thread.sleep(100);
1896                 p.destroy();
1897                 // Subprocess is now dead, but file descriptors remain open.
1898                 check(p.waitFor() != 0);
1899                 check(p.exitValue() != 0);
1900                 stdout.close();
1901                 stderr.close();
1902                 stdin.close();
1903                 //----------------------------------------------------------
1904                 // There remain unsolved issues with asynchronous close.
1905                 // Here's a highly non-portable experiment to demonstrate:
1906                 //----------------------------------------------------------
1907                 if (Boolean.getBoolean("wakeupJeff!")) {
1908                     System.out.println("wakeupJeff!");
1909                     // Initialize signal handler for INTERRUPT_SIGNAL.
1910                     new FileInputStream("/bin/sleep").getChannel().close();
1911                     // Send INTERRUPT_SIGNAL to every thread in this java.
1912                     String[] wakeupJeff = {
1913                         "/bin/bash", "-c",
1914                         "/bin/ps --noheaders -Lfp $PPID | " +
1915                         "/usr/bin/perl -nale 'print $F[3]' | " +
1916                         // INTERRUPT_SIGNAL == 62 on my machine du jour.
1917                         "/usr/bin/xargs kill -62"
1918                     };
1919                     new ProcessBuilder(wakeupJeff).start().waitFor();
1920                     // If wakeupJeff worked, reader probably got EBADF.
1921                     reader.join();
1922                 }
1923             }
1924         } catch (Throwable t) { unexpected(t); }
1925 
1926         //----------------------------------------------------------------
1927         // Attempt to start process with insufficient permissions fails.
1928         //----------------------------------------------------------------
1929         try {
1930             new File("emptyCommand").delete();
1931             new FileOutputStream("emptyCommand").close();
1932             new File("emptyCommand").setExecutable(false);
1933             new ProcessBuilder("./emptyCommand").start();
1934             fail("Expected IOException not thrown");
1935         } catch (IOException e) {
1936             new File("./emptyCommand").delete();
1937             String m = e.getMessage();
1938             if (EnglishUnix.is() &&
1939                 ! matches(m, "Permission denied"))
1940                 unexpected(e);
1941         } catch (Throwable t) { unexpected(t); }
1942 
1943         new File("emptyCommand").delete();
1944 
1945         //----------------------------------------------------------------
1946         // Check for correct security permission behavior
1947         //----------------------------------------------------------------
1948         final Policy policy = new Policy();
1949         Policy.setPolicy(policy);
1950         System.setSecurityManager(new SecurityManager());
1951 
1952         try {
1953             // No permissions required to CREATE a ProcessBuilder
1954             policy.setPermissions(/* Nothing */);
1955             new ProcessBuilder("env").directory(null).directory();
1956             new ProcessBuilder("env").directory(new File("dir")).directory();
1957             new ProcessBuilder("env").command("??").command();
1958         } catch (Throwable t) { unexpected(t); }
1959 
1960         THROWS(SecurityException.class,
1961             new Fun() { void f() throws IOException {
1962                 policy.setPermissions(/* Nothing */);
1963                 System.getenv("foo");}},
1964             new Fun() { void f() throws IOException {
1965                 policy.setPermissions(/* Nothing */);
1966                 System.getenv();}},
1967             new Fun() { void f() throws IOException {
1968                 policy.setPermissions(/* Nothing */);
1969                 new ProcessBuilder("echo").start();}},
1970             new Fun() { void f() throws IOException {
1971                 policy.setPermissions(/* Nothing */);
1972                 Runtime.getRuntime().exec("echo");}},
1973             new Fun() { void f() throws IOException {
1974                 policy.setPermissions(new RuntimePermission("getenv.bar"));
1975                 System.getenv("foo");}});
1976 
1977         try {
1978             policy.setPermissions(new RuntimePermission("getenv.foo"));
1979             System.getenv("foo");
1980 
1981             policy.setPermissions(new RuntimePermission("getenv.*"));
1982             System.getenv("foo");
1983             System.getenv();
1984             new ProcessBuilder().environment();
1985         } catch (Throwable t) { unexpected(t); }
1986 
1987 
1988         final Permission execPermission
1989             = new FilePermission("<<ALL FILES>>", "execute");
1990 
1991         THROWS(SecurityException.class,
1992             new Fun() { void f() throws IOException {
1993                 // environment permission by itself insufficient
1994                 policy.setPermissions(new RuntimePermission("getenv.*"));
1995                 ProcessBuilder pb = new ProcessBuilder("env");
1996                 pb.environment().put("foo","bar");
1997                 pb.start();}},
1998             new Fun() { void f() throws IOException {
1999                  // exec permission by itself insufficient
2000                  policy.setPermissions(execPermission);
2001                  ProcessBuilder pb = new ProcessBuilder("env");
2002                  pb.environment().put("foo","bar");
2003                  pb.start();}});
2004 
2005         try {
2006             // Both permissions? OK.
2007             policy.setPermissions(new RuntimePermission("getenv.*"),
2008                                   execPermission);
2009             ProcessBuilder pb = new ProcessBuilder("env");
2010             pb.environment().put("foo","bar");
2011             Process p = pb.start();
2012             closeStreams(p);
2013         } catch (IOException e) { // OK
2014         } catch (Throwable t) { unexpected(t); }
2015 
2016         try {
2017             // Don't need environment permission unless READING environment
2018             policy.setPermissions(execPermission);
2019             Runtime.getRuntime().exec("env", new String[]{});
2020         } catch (IOException e) { // OK
2021         } catch (Throwable t) { unexpected(t); }
2022 
2023         try {
2024             // Don't need environment permission unless READING environment
2025             policy.setPermissions(execPermission);
2026             new ProcessBuilder("env").start();
2027         } catch (IOException e) { // OK
2028         } catch (Throwable t) { unexpected(t); }
2029 
2030         // Restore "normal" state without a security manager
2031         policy.setPermissions(new RuntimePermission("setSecurityManager"));
2032         System.setSecurityManager(null);
2033 
2034     }
2035 
2036     static void closeStreams(Process p) {
2037         try {
2038             p.getOutputStream().close();
2039             p.getInputStream().close();
2040             p.getErrorStream().close();
2041         } catch (Throwable t) { unexpected(t); }
2042     }
2043 
2044     //----------------------------------------------------------------
2045     // A Policy class designed to make permissions fiddling very easy.
2046     //----------------------------------------------------------------
2047     private static class Policy extends java.security.Policy {
2048         private Permissions perms;
2049 
2050         public void setPermissions(Permission...permissions) {
2051             perms = new Permissions();
2052             for (Permission permission : permissions)
2053                 perms.add(permission);
2054         }
2055 
2056         public Policy() { setPermissions(/* Nothing */); }
2057 
2058         public PermissionCollection getPermissions(CodeSource cs) {
2059             return perms;
2060         }
2061 
2062         public PermissionCollection getPermissions(ProtectionDomain pd) {
2063             return perms;
2064         }
2065 
2066         public boolean implies(ProtectionDomain pd, Permission p) {
2067             return perms.implies(p);
2068         }
2069 
2070         public void refresh() {}
2071     }
2072 
2073     private static class StreamAccumulator extends Thread {
2074         private final InputStream is;
2075         private final StringBuilder sb = new StringBuilder();
2076         private Throwable throwable = null;
2077 
2078         public String result () throws Throwable {
2079             if (throwable != null)
2080                 throw throwable;
2081             return sb.toString();
2082         }
2083 
2084         StreamAccumulator (InputStream is) {
2085             this.is = is;
2086         }
2087 
2088         public void run() {
2089             try {
2090                 Reader r = new InputStreamReader(is);
2091                 char[] buf = new char[4096];
2092                 int n;
2093                 while ((n = r.read(buf)) > 0) {
2094                     sb.append(buf,0,n);
2095                 }
2096             } catch (Throwable t) {
2097                 throwable = t;
2098             } finally {
2099                 try { is.close(); }
2100                 catch (Throwable t) { throwable = t; }
2101             }
2102         }
2103     }
2104 
2105     static ProcessResults run(ProcessBuilder pb) {
2106         try {
2107             return run(pb.start());
2108         } catch (Throwable t) { unexpected(t); return null; }
2109     }
2110 
2111     private static ProcessResults run(Process p) {
2112         Throwable throwable = null;
2113         int exitValue = -1;
2114         String out = "";
2115         String err = "";
2116 
2117         StreamAccumulator outAccumulator =
2118             new StreamAccumulator(p.getInputStream());
2119         StreamAccumulator errAccumulator =
2120             new StreamAccumulator(p.getErrorStream());
2121 
2122         try {
2123             outAccumulator.start();
2124             errAccumulator.start();
2125 
2126             exitValue = p.waitFor();
2127 
2128             outAccumulator.join();
2129             errAccumulator.join();
2130 
2131             out = outAccumulator.result();
2132             err = errAccumulator.result();
2133         } catch (Throwable t) {
2134             throwable = t;
2135         }
2136 
2137         return new ProcessResults(out, err, exitValue, throwable);
2138     }
2139 
2140     //----------------------------------------------------------------
2141     // Results of a command
2142     //----------------------------------------------------------------
2143     private static class ProcessResults {
2144         private final String out;
2145         private final String err;
2146         private final int exitValue;
2147         private final Throwable throwable;
2148 
2149         public ProcessResults(String out,
2150                               String err,
2151                               int exitValue,
2152                               Throwable throwable) {
2153             this.out = out;
2154             this.err = err;
2155             this.exitValue = exitValue;
2156             this.throwable = throwable;
2157         }
2158 
2159         public String out()          { return out; }
2160         public String err()          { return err; }
2161         public int exitValue()       { return exitValue; }
2162         public Throwable throwable() { return throwable; }
2163 
2164         public String toString() {
2165             StringBuilder sb = new StringBuilder();
2166             sb.append("<STDOUT>\n" + out() + "</STDOUT>\n")
2167                 .append("<STDERR>\n" + err() + "</STDERR>\n")
2168                 .append("exitValue = " + exitValue + "\n");
2169             if (throwable != null)
2170                 sb.append(throwable.getStackTrace());
2171             return sb.toString();
2172         }
2173     }
2174 
2175     //--------------------- Infrastructure ---------------------------
2176     static volatile int passed = 0, failed = 0;
2177     static void pass() {passed++;}
2178     static void fail() {failed++; Thread.dumpStack();}
2179     static void fail(String msg) {System.out.println(msg); fail();}
2180     static void unexpected(Throwable t) {failed++; t.printStackTrace();}
2181     static void check(boolean cond) {if (cond) pass(); else fail();}
2182     static void check(boolean cond, String m) {if (cond) pass(); else fail(m);}
2183     static void equal(Object x, Object y) {
2184         if (x == null ? y == null : x.equals(y)) pass();
2185         else fail(x + " not equal to " + y);}
2186 
2187     public static void main(String[] args) throws Throwable {
2188         try {realMain(args);} catch (Throwable t) {unexpected(t);}
2189         System.out.printf("%nPassed = %d, failed = %d%n%n", passed, failed);
2190         if (failed > 0) throw new AssertionError("Some tests failed");}
2191     private static abstract class Fun {abstract void f() throws Throwable;}
2192     static void THROWS(Class<? extends Throwable> k, Fun... fs) {
2193         for (Fun f : fs)
2194             try { f.f(); fail("Expected " + k.getName() + " not thrown"); }
2195             catch (Throwable t) {
2196                 if (k.isAssignableFrom(t.getClass())) pass();
2197                 else unexpected(t);}}
2198 }